module;
#include <cstddef>
#include <cassert>

export module Code_27_2_1;

template<typename T>
class SArray
{
public:
    // create array with initial size
    explicit SArray(std::size_t s)
        : storage(new T[s])
        , storage_size(s)
    {
        init();
    }

    // copy constructor
    SArray(SArray<T> const& orig)
        : storage(new T[orig.size()])
        , storage_size(orig.size())
    {
        copy(orig);
    }

    // destructor: free memory
    ~SArray()
    {
        delete[] storage;
    }

    // assignment operator
    SArray<T>& operator= (SArray<T> const& orig)
    {
        if (&orig != this)
        {
            copy(orig);
        }
        return *this;
    }

    // additive assignment of SArray
    SArray<T>& operator+= (SArray<T> const& b)
    {
        assert(size() == b.size());
        for (std::size_t k = 0; k < size(); ++k)
        {
            (*this)[k] += b[k];
        }
        return *this;
    }

    // multiplicative assignment of SArray
    SArray<T>& operator*= (SArray<T> const& b)
    {
        assert(size() == b.size());
        for (std::size_t k = 0; k < size(); ++k)
        {
            (*this)[k] *= b[k];
        }
        return *this;
    }
    // multiplicative assignment of scalar
    SArray<T>& operator*= (T const& s)
    {
        for (std::size_t k = 0; k < size(); ++k)
        {
            (*this)[k] *= s;
        }
        return *this;
    }

    // return size
    std::size_t size() const
    {
        return storage_size;
    }

    // index operator for constants and variables
    T const& operator[] (std::size_t idx) const
    {
        return storage[idx];
    }
    T& operator[] (std::size_t idx)
    {
        return storage[idx];
    }

protected:
    // init values with default constructor
    void init()
    {
        for (std::size_t idx = 0; idx < size(); ++idx)
        {
            storage[idx] = T();
        }
    }
    // copy values of another array
    void copy(SArray<T> const& orig)
    {
        assert(size() == orig.size());
        for (std::size_t idx = 0; idx < size(); ++idx)
        {
            storage[idx] = orig.storage[idx];
        }
    }

private:
    T* storage;       // storage of the elements
    std::size_t storage_size;  // number of elements
};


//[2]
// helper traits class to select how to refer to an expression template node
// - in general by reference
// - for scalars by value
//[2]
template<typename T> class A_Scalar;
//[2]
// primary template
template<typename T>
class A_Traits 
{
public:
    using ExprRef = T const&;     // type to refer to is constant reference
};
//[2]
// partial specialization for scalars
template<typename T>
class A_Traits<A_Scalar<T>> 
{
public:
    using ExprRef = A_Scalar<T>;  // type to refer to is ordinary value
};


//[1]
// class for objects that represent the addition of two operands
template<typename T, typename OP1, typename OP2>
class A_Add 
{
private:
    typename A_Traits<OP1>::ExprRef op1;    // first operand
    typename A_Traits<OP2>::ExprRef op2;    // second operand

public:
    // constructor initializes references to operands
    A_Add(OP1 const& a, OP2 const& b)
        : op1(a)
        , op2(b) 
    {
    }

    // compute sum when value requested
    T operator[] (std::size_t idx) const 
    {
        return op1[idx] + op2[idx];
    }

    // size is maximum size
    std::size_t size() const 
    {
        assert(op1.size() == 0 || op2.size() == 0 || op1.size() == op2.size());
        return op1.size() != 0 ? op1.size() : op2.size();
    }
};
//[1]
// class for objects that represent the multiplication of two operands
template<typename T, typename OP1, typename OP2>
class A_Mult 
{
private:
    typename A_Traits<OP1>::ExprRef op1;    // first operand
    typename A_Traits<OP2>::ExprRef op2;    // second operand

public:
    // constructor initializes references to operands
    A_Mult(OP1 const& a, OP2 const& b)
        : op1(a)
        , op2(b) 
    {
    }

    // compute product when value requested
    T operator[] (std::size_t idx) const 
    {
        return op1[idx] * op2[idx];
    }

    // size is maximum size
    std::size_t size() const 
    {
        assert(op1.size() == 0 || op2.size() == 0 || op1.size() == op2.size());
        return op1.size() != 0 ? op1.size() : op2.size();
    }
};

//[1]
// class for objects that represent scalars:
template<typename T>
class A_Scalar 
{
private:
    T const& s;  // value of the scalar

public:
    // constructor initializes value
    constexpr A_Scalar(T const& v)
        : s(v) 
    {
    }

    // for index operations, the scalar is the value of each element
    constexpr T const& operator[] (std::size_t) const 
    {
        return s;
    }

    // scalars have zero as size
    constexpr std::size_t size() const {
        return 0;
    };
};


//[5]
template<typename T, typename A1, typename A2>
class A_Subscript
{
public:
    // constructor initializes references to operands
    A_Subscript(A1 const& a, A2 const& b)
        : a1(a)
        , a2(b)
    {
    }

    // process subscription when value requested
    decltype(auto) operator[] (std::size_t idx) const
    {
        return a1[a2[idx]];
    }
    T& operator[] (std::size_t idx)
    {
        return a1[a2[idx]];
    }

    // size is size of inner array
    std::size_t size() const
    {
        return a2.size();
    }
private:
    A1 const& a1;    // reference to first operand
    A2 const& a2;    // reference to second operand
};



//[3]
template<typename T, typename Rep = SArray<T>>
class Array 
{
private:
    Rep expr_rep;   // (access to) the data of the array

public:
    // create array with initial size
    explicit Array(std::size_t s)
        : expr_rep(s) 
    {
    }

    // create array from possible representation
    Array(Rep const& rb)
        : expr_rep(rb) 
    {
    }

    // assignment operator for same type
    Array& operator= (Array const& b) 
    {
        assert(size() == b.size());

        for (std::size_t idx = 0; idx < b.size(); ++idx) 
        {
            expr_rep[idx] = b[idx];
        }
        return *this;
    }

    // assignment operator for arrays of different type
    template<typename T2, typename Rep2>
    Array& operator= (Array<T2, Rep2> const& b)
    {
        assert(size() == b.size());
        for (std::size_t idx = 0; idx < b.size(); ++idx) 
        {
            expr_rep[idx] = b[idx];
        }
        return *this;
    }

    // size is size of represented data
    std::size_t size() const 
    {
        return expr_rep.size();
    }

    // index operator for constants and variables
    decltype(auto) operator[] (std::size_t idx) const 
    {
        assert(idx < size());
        return expr_rep[idx];
    }
    //[6]
    template<typename T2, typename R2>
    Array<T, A_Subscript<T, Rep, R2>> operator[](Array<T2, R2> const& b)
    {
        return Array<T, A_Subscript<T, Rep, R2>>  (A_Subscript<T, Rep, R2>(*this, b));
    }

    T& operator[] (std::size_t idx) 
    {
        assert(idx < size());
        return expr_rep[idx];
    }

    // return what the array currently represents
    Rep const& rep() const 
    {
        return expr_rep;
    }
    Rep& rep() 
    {
        return expr_rep;
    }
};

//[4]
// addition of two Arrays:
template<typename T, typename R1, typename R2>
Array<T, A_Add<T, R1, R2>>  operator+ (Array<T, R1> const& a, Array<T, R2> const& b) 
{
    return Array<T, A_Add<T, R1, R2>> (A_Add<T, R1, R2>(a.rep(), b.rep()));
}

//[4]
// multiplication of two Arrays:
template<typename T, typename R1, typename R2>
Array<T, A_Mult<T, R1, R2>> operator* (Array<T, R1> const& a, Array<T, R2> const& b) 
{
    return Array<T, A_Mult<T, R1, R2>> (A_Mult<T, R1, R2>(a.rep(), b.rep()));
}

// multiplication of scalar and Array:
template<typename T, typename R2>
Array<T, A_Mult<T, A_Scalar<T>, R2>> operator* (T const& s, Array<T, R2> const& b) 
{
    return Array<T, A_Mult<T, A_Scalar<T>, R2>>  (A_Mult<T, A_Scalar<T>, R2>(A_Scalar<T>(s), b.rep()));
}

// multiplication of Array and scalar, addition of scalar and Array
// addition of Array and scalar:
//...





export
namespace Code_27_2_1 {

    void run()
    {

    }
}


